package org.oneblog.data.service.impl;

import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrQuery.SortClause;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.LBHttpSolrServer;
import org.apache.solr.client.solrj.request.FieldAnalysisRequest;
import org.apache.solr.client.solrj.response.AnalysisResponseBase.AnalysisPhase;
import org.apache.solr.client.solrj.response.AnalysisResponseBase.TokenInfo;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.FieldAnalysisResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.oneblog.data.bean.ArticleSolr;
import org.oneblog.data.bean.CategoryCount;
import org.oneblog.data.bean.CategoryFacet;
import org.oneblog.data.bean.HotTag;
import org.oneblog.data.fields.ArticleField;
import org.oneblog.data.query.ArticleSolrQuery;
import org.oneblog.data.rs.Result;
import org.oneblog.data.service.IArticleSolrService;
import org.oneblog.data.util.JunitConsoleOut;
import org.oneblog.data.util.PojoBeanCreator;
import org.oneblog.data.util.QueryStringBuilder;
import org.oneblog.data.util.SolrQueryUtil;
import org.oneblog.data.util.StringTools;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

@Service("articleSolrService")
public class ArticleSolrServiceImpl extends AbstractSolrService implements IArticleSolrService {

    @Override
    public int insertArticles(ArticleSolr article) {
        if (article == null) {
            return 0;
        }
        
        try {
            SolrInputDocument solrInputDocument = article.toSolrInputDocument();
            
            articleSolrServer.add(solrInputDocument);

            articleSolrServer.commit();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
        return 1;
    }

    @Override
    public int updateArticles(ArticleSolr article) {
        if (article == null || StringUtils.isEmpty(article.getArticleId())) {
            return 0;
        }

        try {
            SolrInputDocument solrInputDocument = article.toSolrInputDocument();
            
            articleSolrServer.add(solrInputDocument);

            articleSolrServer.commit();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
        
        return 1;
    }
    
    @Override
    public boolean rebuildAll() {
        int start = 0;
        int rows = 100;
        SolrQuery query = new SolrQuery("*:*");
        query.setRows(rows);
        
        QueryResponse response = null;
        try {
            response = articleSolrServer.query(query);
            
            SolrDocumentList documentList = response.getResults();

            while(!CollectionUtils.isEmpty(documentList)) {

                List<ArticleSolr> articles = new ArrayList<>(documentList.size());
                
                for(SolrDocument d : documentList) {
                    ArticleSolr articleSolr = ArticleSolr.createFrom(d);
                    if(articleSolr != null) {
                        articles.add(articleSolr);
                    }
                }
                
                // 更新索引
                update(articles);
                
                start += rows;
                query.setStart(start);
                response = articleSolrServer.query(query);
                documentList = response.getResults();
            }
        } catch (SolrServerException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    
    private void update(List<ArticleSolr> articles) {
        if(CollectionUtils.isEmpty(articles)) {
            return ;
        }
        List<SolrInputDocument> docs = new ArrayList<>(articles.size());
        try {
            
            for(ArticleSolr a : articles) {
                SolrInputDocument d = a.toSolrInputDocument();
                if(d != null) {
                    docs.add(d);
                }
            }
            
            articleSolrServer.add(docs);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                articleSolrServer.commit();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
    }
    
    @Override
    public ArticleSolr getById(String articleId) {
        if(StringUtils.isEmpty(articleId)) {
            return null;
        }
        
        try {
            SolrQuery query = new SolrQuery(ArticleField.articleId + ":" + articleId);
            query.setRows(1);
            QueryResponse response = articleSolrServer.query(query);
            
            SolrDocumentList documentList = response.getResults();
            
            if(documentList.size() == 0) {
                return null;
            }
            
            return ArticleSolr.createFrom(documentList.get(0));
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    @Override
    public Result<ArticleSolr> queryArticles(ArticleSolrQuery query) {
        
        try {

            boolean hasKeyword = false;

            Result<ArticleSolr> result = new Result<>();
            
            SolrQuery solrQuery = new SolrQuery("*:*");
            
            QueryStringBuilder builder = new QueryStringBuilder();
            
            String keyword = query.getKeyword();
            if(StringUtils.isNotBlank(keyword)) {
                hasKeyword = true;
                String keywordTxt = "\"" + keyword + "\"~55";
                QueryStringBuilder keywordBuilder = new QueryStringBuilder();
                keywordBuilder.addSubQuery(ArticleField.articleTitle, keywordTxt, 5000);
                
                String[] words = keyword.split("\\s+");
                if(words.length == 1) {
                    keywordBuilder.addSubQuery(JunitConsoleOut.console("({}:{}^{} OR {}:{}^{})", new Object[] {
                            ArticleField.TAG_TEXT, keywordTxt, 400,
                            ArticleField.tags, keyword, 400
                    }));
                } else {
                    for(String w : words) {
                        keywordBuilder.addSubQuery(JunitConsoleOut.console("({}:{}^{} OR {}:{}^{})", new Object[] {
                                ArticleField.TAG_TEXT, "\"" + w + "\"~55", 400,
                                ArticleField.tags, w, 400
                        }));
                    }
                }
                
                keywordBuilder.addSubQuery(ArticleField.articleContent, keywordTxt, 50);
                builder.addSubQuery(keywordBuilder.buildOrQueryString());
            }
            
            String tag = query.getTag();
            if(StringUtils.isNotBlank(tag)) {
                builder.addSubQuery(ArticleField.tags, tag);
            }
            
            Integer categoryId = StringTools.getInt(query.getCategoryId(), -1);
            if(categoryId > 0) {
            	builder.addSubQuery(ArticleField.categoryId, categoryId);
            }
            
            String queryString = builder.buildAndQueryString();
            if(StringUtils.isNotBlank(queryString)) {
                solrQuery.setQuery(queryString);
            }
            
            if(!hasKeyword) {
                solrQuery.setSort(ArticleField.createTime, ORDER.desc);
            }
            
            solrQuery.setStart(query.getFirst()).setRows(query.getMax());
            
            setHighLighter(solrQuery, query);
            
            System.out.println(URLDecoder.decode(solrQuery.toString(), "UTF-8"));
            
            QueryResponse response = articleSolrServer.query(solrQuery);
            // 获取相关的高亮信息
            Map<String, Map<String, List<String>>> high = response.getHighlighting();
            
            SolrDocumentList solrDocumentList = response.getResults();
            result.setTotal(solrDocumentList.getNumFound());
            
            List<ArticleSolr> list = new ArrayList<ArticleSolr>();
            for(SolrDocument d : solrDocumentList) {

                ArticleSolr a = ArticleSolr.createFrom(d);
                String fullContent = a.getArticleContent();
                if(hasKeyword) {
                    String key = a.getArticleId();
                    a.setArticleTitle(resetContent(getHighLight(high, key, ArticleField.articleTitle, a.getArticleTitle())));
                    a.setArticleContent(resetContent(getHighLight(high, key, ArticleField.articleContent, a.getArticleContent())));
                } else {
                    a.setArticleContent(cutContent(a.getArticleContent()));
                }
                a.setArticleContent(attachContent(a.getArticleContent(), fullContent));
                list.add(a);
            }
            result.setList(list);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    static final int max = 700;
    private static String attachContent(String old, String full) {
        if(old == null) {
            return old;
        }
        if(old.length() < 700) {
            return (old + full).substring(0, 700);
        }
        return old;
    }
    private static String cutContent(String s) {
        if(s == null) {
            return s;
        }
        s = s.replaceAll("\"", "\\\"");
        if(s.length() < max) {
            return s;
        }
        return StringTools.cutForABC(s, max * 2);
    }
    private static String resetContent(String s) {
        if(s == null) {
            return s;
        }
        s = StringTools.cutForABC(s, s.length() * 2);
        s = s.replaceAll(HILIGNT_TAG_S, SPAN_S);
        s = s.replaceAll(HILIGNT_TAG_E, SPAN_E);

        return s;
    }
    
    @Override
    public int updateArticleBroswerCount(String articleId, int count) {
        if(StringUtils.isBlank(articleId)) {
            return 0;
        }
        
        return updateArticleDocument(articleId, ArticleField.browserCount, count);
    }
    
    private int updateArticleDocument(String uniqueId, String field, Object value) {

        SolrInputDocument updateDoc = new SolrInputDocument();

        updateDoc.setField(ArticleField.articleId, uniqueId);
        
        Map<String, Object> updateMap = new HashMap<String, Object>();
        updateMap.put("set", value);
        updateDoc.setField(field, updateMap);
        
        try {
            UpdateResponse response = articleSolrServer.add(updateDoc);
            
            return response != null ? 1 : 0;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    
    @Override
    public int increaseGoodCount(String articleId) {
        ArticleSolr articleSolr = getById(articleId);
        if(articleSolr == null) {
            return 0;
        }
        return updateArticleDocument(articleId, ArticleField.goodCount, articleSolr.getGoodCount() + 1);
    }
    
    @Override
    public List<HotTag> getHotTags() {
        
        try {
            SolrQuery solrQuery = new SolrQuery("*:*");
            solrQuery.setRows(0);
            
            solrQuery.setFacet(true).addFacetField(ArticleField.tags)
            .setFacetLimit(15);
            
            QueryResponse queryResponse = articleSolrServer.query(solrQuery);
            
            FacetField facetField = queryResponse.getFacetField(ArticleField.tags);
            
            List<Count> values = facetField.getValues();
            
            if(!CollectionUtils.isEmpty(values)) {
                List<HotTag> tags = new ArrayList<HotTag>();
                
                for(Count c : values) {
                    tags.add(new HotTag(c.getName(), (int)c.getCount()));
                }
                
                return resetTags(tags);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return new ArrayList<>(0);
    }
    
    //根据小写标签，合并
    private List<HotTag> resetTags(List<HotTag> tags) {
        
        if(CollectionUtils.isEmpty(tags)) {
            return tags;
        }
        Map<String, HotTag> tempMap = new HashMap<>();
        
        List<HotTag> rs = new ArrayList<>(tags.size());
        
        for(HotTag t : tags) {
            String tag = t.getTag();
            if(StringUtils.isBlank(tag)) {
                continue;
            }
            tag = tag.toLowerCase();
            HotTag tempTag = tempMap.get(tag);
            if(tempTag != null) {//已存在
                tempTag.setCount(tempTag.getCount() + t.getCount());
            } else {
                rs.add(t);
                tempMap.put(tag, t);
            }
        }
        
        return rs;
    }
    
    @Override
    public List<ArticleSolr> queryHotArticles(int limit) {
        
        try {
            QueryStringBuilder queryStringBuilder = new QueryStringBuilder();
            queryStringBuilder.addSubQuery("*:*");

            SolrQuery query = new SolrQuery(queryStringBuilder.buildAndQueryString());
            
            query.setSort(ArticleField.browserCount, ORDER.desc);
//            query.setFields(ArticleField.articleId, ArticleField.articleTitle, ArticleField.browserCount);
            query.setRows(limit);

            return queryArticles(query);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
    @Override
    public List<ArticleSolr> queryPriority(int limit) {
        
        try {
            int minPriority = 5;
            QueryStringBuilder queryStringBuilder = new QueryStringBuilder();
            queryStringBuilder.addSubQuery(ArticleField.priority, "[" + minPriority + " TO *]");

            SolrQuery query = new SolrQuery(queryStringBuilder.buildAndQueryString());
            
            List<SortClause> sorts = new ArrayList<SolrQuery.SortClause>();
            sorts.add(new SortClause(ArticleField.priority, ORDER.desc));
            sorts.add(new SortClause(ArticleField.browserCount, ORDER.desc));
            query.setSorts(sorts);
//            query.setFields(ArticleField.articleId, ArticleField.articleTitle);
            query.setRows(limit);
            
            return queryArticles(query);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
    private List<ArticleSolr> queryArticles(SolrQuery query) {
        try {
            
            QueryResponse queryResponse = articleSolrServer.query(query);
            
            SolrDocumentList results = queryResponse.getResults();
            
            List<ArticleSolr> articles = new ArrayList<ArticleSolr>();
            for(SolrDocument d : results) {
            	ArticleSolr a = ArticleSolr.createFrom(d);

                a.setArticleContent(resetContent(a.getArticleContent()));
                
                articles.add(a);
            }
            return articles;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ArrayList<ArticleSolr>(0);
    }
    
    @Override
    public CategoryFacet categoryFacet() {
    	 try {
    		 
             SolrQuery solrQuery = new SolrQuery("*:*");
             solrQuery.setRows(0);
             
             solrQuery.setFacet(true).addFacetField(ArticleField.categoryId)
             .setFacetLimit(15);
             
             QueryResponse queryResponse = articleSolrServer.query(solrQuery);
             
             FacetField facetField = queryResponse.getFacetField(ArticleField.categoryId);
             
             List<Count> values = facetField.getValues();
             //统计每个分类的数量
             if(!CollectionUtils.isEmpty(values)) {
                 Map<Integer, Integer> map = new HashMap<Integer, Integer>();
                 for(Count c : values) {
                	 map.put(StringTools.getInt(c.getName(), -1), (int) c.getCount());
                 }
                 
                 CategoryFacet facet = new CategoryFacet();
                 facet.setCategory1(new CategoryCount(1, "", map.get(1)));
                 facet.setCategory2(new CategoryCount(2, "", map.get(2)));
                 facet.setCategory3(new CategoryCount(3, "", map.get(3)));
                 facet.setCategory4(new CategoryCount(4, "", map.get(4)));
                 facet.setCategory5(new CategoryCount(5, "", map.get(5)));
                 return facet;
             }
             
         } catch (Exception e) {
             e.printStackTrace();
         }
         
    	return null;
    }
    
    @Override
    public List<ArticleSolr> queryRelativeArticles(String id, String title, List<String> tags) {
        List<String> words = analyzeBySolrType(title, articleSolrServer, true);
        
        try {
            QueryStringBuilder queryStringBuilder = new QueryStringBuilder();
            
            if(words != null && words.size() > 0) {
                for(String s : words) {
                    queryStringBuilder.addSubQuery(ArticleField.articleTitle, s, s.length() * 80);
                }
            }

            if(tags != null && tags.size() > 0) {
                for(String s : tags) {
                    queryStringBuilder.addSubQuery(ArticleField.tags, s, 200);
                }
            }

            SolrQuery query = new SolrQuery(queryStringBuilder.buildOrQueryString());
            query.setFields(ArticleField.articleId, ArticleField.articleTitle, "score");
            
//            System.out.println(URLDecoder.decode(query.getQuery(), "utf-8"));
            
            query.setSort(ArticleField.createTime, ORDER.asc);
            
            query.setRows(10);

            QueryResponse queryResponse = articleSolrServer.query(query);
            
            SolrDocumentList results = queryResponse.getResults();
            
            List<ArticleSolr> articles = new ArrayList<ArticleSolr>();
            for(SolrDocument d : results) {
                ArticleSolr a = ArticleSolr.createFrom(d);
                
                if(PojoBeanCreator.getDoubleValue(d, "score", 0) < 0.1D
                        || id.equalsIgnoreCase(a.getArticleId())) {
                    continue;
                }

                a.setArticleContent(resetContent(a.getArticleContent()));
                
                articles.add(a);
            }
            return articles;
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
    
    private List<String> analyzeBySolrType(String word, LBHttpSolrServer server, Boolean useSmart) {

        List<String> rs = new ArrayList<String>(0);
        try {
            if(StringUtils.isBlank(word) || word.length() < 2) {
                return rs;
            }
            
            FieldAnalysisRequest request = new FieldAnalysisRequest();
            
            //设置分词域
            request.addFieldName(ArticleField.articleTitle);
            //设置待分词字符串
            request.setFieldValue(word);
            request.setQuery(word);
            
            //查询分词结果
            FieldAnalysisResponse response = request.process(server);
            
            if(response == null) {
                return rs;
            }
            
            for(Entry<String, FieldAnalysisResponse.Analysis> r : response.getAllFieldNameAnalysis()){
                FieldAnalysisResponse.Analysis ana = r.getValue();
                Iterator<AnalysisPhase> ps = null;
                if(useSmart) {
                    ps = ana.getQueryPhases().iterator();
                } else {
                    ps = ana.getIndexPhases().iterator();
                }
                while(ps.hasNext()) {
                    AnalysisPhase p = ps.next();
                    for(TokenInfo ti : p.getTokens()) {
                        String w = ti.getText();
                        if(w.length() > 1) {
                            rs.add(w);
                        }
                    }
                    if(p.getClassName().equalsIgnoreCase("org.wltea.analyzer.lucene.IKTokenizer")) {
                        return rs;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return rs;
    }
    
    @Override
    public List<String> getAllArticleIds() {
        try {
            SolrQuery solrQuery = new SolrQuery("*:*");
            solrQuery.setRows(500);
            
            solrQuery.setFields(ArticleField.articleId);
            solrQuery.setSort(ArticleField.updateTime, ORDER.desc);
            
            QueryResponse queryResponse = articleSolrServer.query(solrQuery);
            
            SolrDocumentList results = queryResponse.getResults();

            List<String> ids = new ArrayList<String>();
            
            for(SolrDocument d : results) {
                ids.add(PojoBeanCreator.getDocValue(d.get(ArticleField.articleId)));
            }
            return ids;
        } catch (Exception e) {
            
        }
        return null;
    }
    
    @Override
    public List<ArticleSolr> getArticlesForIds(List<String> ids) {
    	if(CollectionUtils.isEmpty(ids)) {
    		return null;
    	}
    	try {
    		QueryStringBuilder queryStringBuilder = new QueryStringBuilder();
           
    		queryStringBuilder.addSubQuery(SolrQueryUtil.buildQuery(ids, ArticleField.articleId, "OR"));

            SolrQuery query = new SolrQuery(queryStringBuilder.buildAndQueryString());
            query.setFields(ArticleField.articleId, ArticleField.articleTitle, ArticleField.createTime, 
            		ArticleField.THUMBNAIL_IMG_URL, ArticleField.articleContent, ArticleField.tags, ArticleField.browserCount,
            		ArticleField.srcType, ArticleField.updateTime);
            
            query.setRows(ids.size());

            QueryResponse queryResponse = articleSolrServer.query(query);
            
            SolrDocumentList results = queryResponse.getResults();
            
            List<ArticleSolr> articles = new ArrayList<ArticleSolr>();
            for(SolrDocument d : results) {
                ArticleSolr a = ArticleSolr.createFrom(d);

                a.setArticleContent(resetContent(a.getArticleContent()));
                
                articles.add(a);
            }
            return articles;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
    	return null;
    }
    
    private static String fillTag(String s) {
        if(s == null) {
            return s;
        }
        boolean isNeedFill = false;
        for(int i = 0, len = s.length(); i < len; i ++) {
            char ch = s.charAt(i);
            if(ch == '<') {
                isNeedFill = true;
            } else if(ch == '>') {
                isNeedFill = false;
            }
        }
        if(isNeedFill) {
            return s + "/>";
        }
        return s;
    }
    public static void main(String[] args) {
        String s = "### 前言\n" + 
                "Lucene中一个很重要的概念就是文档（Document），它代表一条建立索引的独立且完整的数据。可以对标到我们关系数据库的一条记录。一个Document包含很多个域（Field），对标数据库的字段Column。Field的一些属性配置对标字段的属性。\n" + 
                "\n" + 
                "本身Lucene对Document的Field是开放式的，不同Field的Document可以索引到一起，有点类似于noSQL的概念，属于schema-free的。但是这种开放式结构会造成“开发一时爽，维护骂爹娘”的情况，所以<span class=\"keywordHighLight\">Solr</span>在封装Lucene的时候通过`schema.xml`文件来规范Document的Field定义。类似于MongoDB的一些ORM框架（Morephia、spring-data-mongo）做的事，其实就是定义一个标准、做个存根，方便排查。\n" + 
                "\n" + 
                "所以，`schema.xml`配置的内容就出来了：\n" + 
                "+ Field Type 定义：定义了字段类型，string、int、double、text等等，名字是自取的。\n" + 
                "+ Field 定义：字段，比如position_id、name、age等等。\n" + 
                "\n" + 
                "------\n" + 
                "\n" + 
                "### schema.xml的大致结构\n" + 
                "```XML\n" + 
                "<?xml version=\"1.0\" ?>\n" + 
                "<schema name=\"position\" version=\"1.1\">\n" + 
                "    <types>\n" + 
                "        <fieldtype name=\"string\" class=\"solr.StrField\" sortMissingLast=\"true\" omitNorms=\"true\"/>\n" + 
                "        <fieldType name=\"long\" class=\"solr.TrieLongField\" precisionStep=\"0\" positionIncrementGap\"";
        
        System.out.println(fillTag(s));
        
        System.out.println(cutContent(s));
    }

    @Resource(name = "articleSolrServer")
    private LBHttpSolrServer articleSolrServer;
}
